package com.chenfan.mcn.extension.queue;

import com.chenfan.common.extension.context.TraceContext;
import com.chenfan.common.extension.context.TraceContextHolder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.Objects;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * @author wulg
 * @date 2023/11/8
 */
@Slf4j
public class SmartDelayQueue {


    /** 延迟队列 **/
    private static DelayQueue<EventDelay> queue = new DelayQueue<>();

    static {
        Thread delayThread = new Thread(() -> {
            while (true) {
                try {
                    EventDelay delay = queue.take();
                    if(Objects.nonNull(delay)) {
                        TraceContextHolder.setContext(delay.getTraceContext());
                        delay.getRunnable().run();
                    } else {
                        Thread.sleep(1000L);
                    }
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                } finally {
                    TraceContextHolder.clear();
                }
            }
        });
        delayThread.setDaemon(true);
        delayThread.setName("DelayTask-Thread");
        delayThread.start();
    }

    /**
     * 发送
     * @param runnable
     * @param <T>
     */
    public static <T> void post(Runnable runnable) {
        queue.put(new EventDelay(new Date(), runnable));
    }

    /**
     * 发送
     * @param runnable
     * @param delay
     * @param timeUnit
     * @param <T>
     */
    public static <T> void post(Runnable runnable, long delay, TimeUnit timeUnit) {
        queue.put(new EventDelay(new Date(), runnable, timeUnit.toMillis(delay)));
    }

    /**
     * 事件delay
     * @author wulg
     * @date 2023/9/22
     */
    @Getter
    public static class EventDelay implements Delayed {

        private static final long DEFAULT_DELAY_MS = 5 * 1000L;

        private Date taskTime;

        /**
         * 事件
         */
        private Runnable runnable;

        private TraceContext traceContext;

        private long delayMS;

        public EventDelay(Date taskTime, Runnable runnable) {
            this(taskTime, runnable, DEFAULT_DELAY_MS);
            traceContext = TraceContextHolder.getCurrentContext();
        }

        public EventDelay(Date taskTime, Runnable runnable, long delayMS) {
            this.taskTime = taskTime;
            this.runnable = runnable;
            this.delayMS = delayMS;
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return taskTime.getTime() + delayMS - System.currentTimeMillis();
        }

        @Override
        public int compareTo(Delayed o) {
            return this.taskTime.getTime() - ((EventDelay) o).taskTime.getTime() > 0 ? 1 : -1;
        }
    }
}
